window.requestAnimFrame = function () {
    return (
        window.requestAnimationFrame ||
        window.webkitRequestAnimationFrame ||
        window.mozRequestAnimationFrame ||
        window.oRequestAnimationFrame ||
        window.msRequestAnimationFrame ||
        function (callback) {
            window.setTimeout(callback)
        }
    )
}

function init(elemid) {
    let canvas = document.getElementById(elemid)
    c = canvas.getContext('2d')
    w = (canvas.width = window.innerWidth)
    h = (canvas.height = window.innerHeight)
    c.fillStyle = "rgba(30,30,30,1)"
    c.fillRect(0, 0, w, h)
    return { c: c, canvas: canvas }
}

window.onload = function () {
    let c = init("canvas").c,
        canvas = init("canvas").canvas,
        w = (canvas.width = window.innerWidth),
        h = (canvas.height = window.innerHeight),
        mouse = { x: false, y: false },
        last_mouse = {}

    function dist(p1x, p1y, p2x, p2y) {
        return Math.sqrt(Math.pow(p2x - p1x, 2) + Math.pow(p2y - p1y, 2))
    }

    class segment {
        constructor(parent, l, a, first) {
            this.first = first
            if (first) {
                this.pos = {
                    x: parent.x,
                    y: parent.y,
                }
            } else {
                this.pos = {
                    x: parent.nextPos.x,
                    y: parent.nextPos.y,
                }
            }
            this.l = l
            this.ang = a
            this.nextPos = {
                x: this.pos.x + this.l * Math.cos(this.ang),
                y: this.pos.y + this.l * Math.sin(this.ang),
            }
        }

        update(t) {
            this.ang = Math.atan2(t.y - this.pos.y, t.x - this.pos.x)
            this.pos.x = t.x + this.l * Math.cos(this.ang - Math.PI)
            this.pos.y = t.y + this.l * Math.sin(this.ang - Math.PI)
            this.nextPos.x = this.pos.x + this.l * Math.cos(this.ang)
            this.nextPos.y = this.pos.y + this.l * Math.sin(this.ang)
        }

        fallback(t) {
            this.pos.x = t.x
            this.pos.y = t.y
            this.nextPos.x = this.pos.x + this.l * Math.cos(this.ang)
            this.nextPos.y = this.pos.y + this.l * Math.sin(this.ang)
        }

        show() {
            c.lineTo(this.nextPos.x, this.nextPos.y)
        }
    }

    class tentacle {
        constructor(x, y, l, n, a) {
            this.x = x
            this.y = y
            this.l = l
            this.n = n
            this.t = {}
            this.rand = Math.random()
            this.segments = [new segment(this, this.l / this.n, 0, true)]
            for (let i = 1; i < this.n; i++) {
                this.segments.push(
                    new segment(this.segments[i - 1], this.l / this.n, 0, false)
                )
            }
        }

        move(last_target, target) {
            this.angle = Math.atan2(target.y - this.y, target.x - this.x)
            this.dt = dist(last_target.x, last_target.y, target.x, target.y) + 5
            this.t = {
                x: target.x - 0.8 * this.dt * Math.cos(this.angle),
                y: target.y - 0.8 * this.dt * Math.sin(this.angle)
            }
            if (this.t.x) {
                this.segments[this.n - 1].update(this.t)
            } else {
                this.segments[this.n - 1].update(target)
            }
            for (let i = this.n - 2; i >= 0; i--) {
                this.segments[i].update(this.segments[i + 1].pos)
            }
            if (
                dist(this.x, this.y, target.x, target.y) <=
                this.l + dist(last_target.x, last_target.y, target.x, target.y)
            ) {
                this.segments[0].fallback({ x: this.x, y: this.y })
                for (let i = 1; i < this.n; i++) {
                    this.segments[i].fallback(this.segments[i - 1].nextPos)
                }
            }
        }

        show(target) {
            c.beginPath()
            c.lineTo(this.x, this.y)
            for (let i = 0; i < this.n; i++) {
                this.segments[i].show()
            }
            c.strokeStyle = "hsl(" + (this.rand * 60 + 180) + ",100%," + (this.rand * 60 + 25) + "%)"
            c.lineWidth = this.rand * 2
            c.lineCap = "round"
            c.lineJoin = "round"
            c.stroke()
            c.beginPath()
        }
    }

    let tentacles = []
    for (let i = 0; i < 30; i++) {
        tentacles.push(
            new tentacle(
                Math.random() * w,
                Math.random() * h,
                Math.random() * 50 + 50,
                Math.random() * 10 + 10,
                Math.random() * 2 * Math.PI
            )
        )
    }

    canvas.addEventListener(
        "mousemove",
        function (e) {
            last_mouse.x = mouse.x
            last_mouse.y = mouse.y

            mouse.x = e.pageX - this.offsetLeft
            mouse.y = e.pageY - this.offsetTop
        },
        false
    )

    canvas.addEventListener("mouseleave", function (e) {
        mouse.x = false
        mouse.y = false
    })

    function loop() {
        window.requestAnimFrame(loop)
        c.fillStyle = "rgba(30,30,30,0.1)"
        c.fillRect(0, 0, w, h)

        if (mouse.x) {
            let target = { x: mouse.x, y: mouse.y }
            let last_target = { x: last_mouse.x, y: last_mouse.y }

            tentacles.forEach(function (t) {
                t.move(last_target, target)
                t.show(target)
            })
        }
    }

    loop()
}